#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<string.h>
#include<assert.h>
#include<ctype.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>


#define NUM 1024
#define OPT_NUM 64


#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define trimSpace(start)  do{\
          while(isspace(*start))   ++start;\
        }while(0)


char LineCommand[NUM];
char *myargv[OPT_NUM];


int lastCode = 0;
int lastSig = 0;


int redirType = NONE_REDIR;
char* redirFile = NULL;
void commandCheck(char* commands)
{
  assert(commands);
  char* start = commands;
  char* end = commands + strlen(commands);
  while(start < end)
  {
    if(*start == '>')
    {
      *start = '\0';
      start++;
      if(*start == '>')
      {
        redirType = APPEND_REDIR;
        start++;
      }
      else
      {
        redirType = OUTPUT_REDIR;
      }
      trimSpace(start);
      redirFile = start;
      break;
    }
    else if(*start == '<')
    {
      *start = '\0';
      start++;
      trimSpace(start);
      redirType = INPUT_REDIR;
      redirFile = start;
      break;
    }
    else
    {
      start++;
    }
  }
}
int main()
{
  while(1){
    redirType = NONE_REDIR;
    redirFile = NULL;
    errno = 0;
    //输出提示符
    printf("用户名@主机名 当前路径# ");
    fflush(stdout);
    //获取用户输入，输入的时候，输入\n
    char* s = fgets(LineCommand,sizeof(LineCommand) - 1,stdin);
    assert(s != NULL);
    (void)s;//不明白
    //清除最后一个\n，abcd\n
    LineCommand[strlen(LineCommand) - 1] = 0;
    //printf("test: %s\n",LineCommand);
    //"ls -a -l -i"->"ls" "-a" ....
    //"ls -a -l > myfile.txt"
    commandCheck(LineCommand);
    //字符串切割
    myargv[0] = strtok(LineCommand," ");
    int i = 1;
    if(myargv[0] != NULL && strcmp(myargv[0],"ls") == 0)
    {
      myargv[i++] = (char*)"--color=auto";
    }
    while(myargv[i++] = strtok(NULL," "));
    //如果是cd命令，不需要创建子进程，让shell自己执行对应的命令，本质就是执行系统接口
    //像这种不需要让我们的子进程来执行，而是让shell自己执行的命令叫内建或内置命令
    if(myargv[0] != NULL && strcmp(myargv[0],"cd") == 0)
    {
      if(myargv[1] != NULL)
        chdir(myargv[1]);
      continue;
    }
    if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0],"echo") == 0)
    {
      if(strcmp(myargv[1],"$?") == 0)
      {
        printf("%d, %d\n",lastCode,lastSig);
      }
      else{
        printf("%s\n",myargv[1]);
      }
      continue;
    }
    //条件编译
  #ifdef DEBUG
    for(int i = 0;myargv[i];i++)
    {
      printf("myargv[%d]:%s\n",i,myargv[i]);
    }
  #endif
    //执行命令
    pid_t id = fork();
    assert(id != -1);
    if(id == 0)
    {
      //因为命令是子进程执行的，真正重定向的工作一定要是子进程来完成
      //如何重定向，是父进程要给子进程提供信息的
      //这里重定向是不会影响父进程的因为进程具有独立性
      switch(redirType)
      {
        case NONE_REDIR:
          break;
        case INPUT_REDIR:
          {
          int fd = open(redirFile,O_RDONLY);
          if(fd < 0)
          {
            perror("open");
            exit(errno);
          }
          dup2(fd,0);
          }
          break;
        case OUTPUT_REDIR:
        case APPEND_REDIR:
          {
            umask(0);
            int flags = O_WRONLY | O_CREAT;
            if(redirType == APPEND_REDIR)
              flags |= O_APPEND;
            else
              flags |= O_TRUNC;
            int fd = open(redirFile,flags,0666);
            if(fd < 0)
            {
              perror("open");
              exit(errno);
            }
            dup2(fd,1);
          }
          break;
        default:
          printf("bug?\n");
          break;
      }
      execvp(myargv[0],myargv);//执行程序替换的时候，不会影响曾经进程打开的重定向的文件
      exit(1);
    }
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    assert(ret>0);
    (void)ret;//不理解
    lastCode = ((status>>8) & 0xFF);
    lastSig = (status & 0x7F);
  }
  return 0;
}
